﻿#region Copyright and License Notice
// Copyright (C)2010-2016 - Rob Levine and other contributors
// http://configgen.codeplex.com
// 
// This file is part of ConfigGen.
// 
// ConfigGen is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// ConfigGen is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License and 
// the GNU Lesser General Public License along with ConfigGen.  
// If not, see <http://www.gnu.org/licenses/>
#endregion

using System;
using System.Diagnostics;
using System.IO;
using System.Xml.Serialization;
using ConfigGen.Core;
using ConfigGen.Utilities;
using ConfigGen.Utilities.Extensions.System.IO;
using NUnit.Framework;

namespace ConfigGen.Applications.Tests.NAnt
{
    /// <summary>
    /// This suite of tests invokes the ConfigGen NAnt task via NAnt from the command line.
    /// Therefore, some of these tests are more integration than unit.
    /// </summary>
    [TestFixture]
    public class ConfigGenTaskTests
    {
        /// <summary>
        /// Calls NAnt with known settings in the build file, and asserts that the ConfigGen core reports the correct settings when
        /// in verbose mode. This is one of two tests, the other (<see cref="NAntTaskSettingsPassedInCorrectly002"/>) designed to set the same settings to different values as a counterpoint test.
        /// </summary>
        [Test]
        public void NAntTaskSettingsPassedInCorrectly001()
        {
            const string buildFileContents = @"
<project name=""ConfigGen NAnt Task"" default=""test"" basedir=""."">
  <target name=""test"">
    <loadtasks assembly=""ConfigGen.Applications.dll"" />
    <configgen
        dumpSettingsOnly=""true""
        dumpSettingsFile=""{0}""
        addMachineNameSuffix=""false""
        forceName=""""
        inhibitWrite=""false""
        localOnly=""false""
        machineNames=""""
        outputDirectory=""Configs""
        prettyPrint=""false""
        prettyPrintLineLength=""1""
        settingsFile=""App.Config.Settings.xls""
        flatDirectoryOutput=""false""
        templateFile=""App.Config.Template.xml""
        writeUnchangedFiles=""false""
        verbose=""false""/>
  </target>  
</project>";

            var results = ExecuteTest(buildFileContents);
            Assert.AreEqual(0, results.ExitCode, "Exit Code: incorrect");

            var settings = results.Settings;

            Assert.IsTrue(settings.DumpSettingsOnly, "DumpSettingsOnly: incorrect");
            Assert.IsFalse(settings.MachineSuffix, "MachineSuffix: incorrect");
            Assert.AreEqual(string.Empty, settings.ForcedFilename, "ForcedFilename: incorrect");
            Assert.IsFalse(settings.InhibitWrite, "InhibitWrite: incorrect");
            Assert.IsFalse(settings.LocalOnly, "LocalOnly: incorrect");
            Assert.IsNull(settings.SpecifiedMachines, "SpecifiedMachineNames: incorrect");
            Assert.AreEqual("Configs", settings.OutputDirectory, "OutputDirectory: incorrect");
            Assert.IsFalse(settings.PrettyPrint, "PrettyPrint: incorrect");
            Assert.AreEqual(1, settings.PrettyPrintLineLength, "PrettyPrintLineLength: incorrect");
            Assert.AreEqual("App.Config.Settings.xls", settings.SettingsFile, "SettingsFile: incorrect");
            Assert.IsFalse(settings.FlatDirectoryOutput, "FlatDirectoryOutput: incorrect");
            Assert.AreEqual("App.Config.Template.xml", settings.TemplateFile, "TemplateFile: incorrect");
            Assert.IsFalse(settings.WriteUnchangedFiles, "WriteUnchangedFiles: incorrect");
            Assert.IsFalse(settings.Verbose, "Verbose: incorrect");
        }

        /// <summary>
        /// Calls NAnt with known settings in the build file, and asserts that the ConfigGen core reports the correct settings when
        /// in verbose mode. This is one of two tests, the other (<see cref="NAntTaskSettingsPassedInCorrectly001"/>) designed to set the same settings to different values as a counterpoint test.
        /// </summary>
        [Test]
        public void NAntTaskSettingsPassedInCorrectly002()
        {
            const string buildFileContents = @"
<project name=""ConfigGen NAnt Task"" default=""test"" basedir=""."">
  <target name=""test"">
    <loadtasks assembly=""ConfigGen.Applications.dll"" />
    <configgen
        dumpSettingsOnly=""true""
        dumpSettingsFile=""{0}""
        addMachineNameSuffix=""true""
        forceName=""ForcedName""
        inhibitWrite=""true""
        localOnly=""true""
        machineNames=""machine1 machine2""
        outputDirectory=""OtherConfigs""
        prettyPrint=""true""
        prettyPrintLineLength=""10""
        settingsFile=""App.Config.Settings.Alternate.xls""
        flatDirectoryOutput=""true""
        templateFile=""App.Config.Template.Alternate.xml""
        writeUnchangedFiles=""true""
        verbose=""true""/>
  </target>  
</project>";
            
            var results = ExecuteTest(buildFileContents);
            Assert.AreEqual(0, results.ExitCode, "Exit Code: incorrect");

            var settings = results.Settings;

            Assert.IsTrue(settings.MachineSuffix, "MachineSuffix: incorrect");
            Assert.AreEqual("ForcedName", settings.ForcedFilename, "ForcedFilename: incorrect");
            Assert.IsTrue(settings.InhibitWrite, "InhibitWrite: incorrect");
            Assert.IsTrue(settings.LocalOnly, "LocalOnly: incorrect");
            Assert.IsNotNull(settings.SpecifiedMachines, "SpecifiedMachineNames: incorrect");
            Assert.AreEqual(2, settings.SpecifiedMachines.Length, "SpecifiedMachineNames: incorrect length");
            Assert.AreEqual("machine1", settings.SpecifiedMachines[0], "SpecifiedMachineNames[0]: incorrect");
            Assert.AreEqual("machine2", settings.SpecifiedMachines[1], "SpecifiedMachineNames[1]: incorrect");
            Assert.AreEqual("OtherConfigs", settings.OutputDirectory, "OutputDirectory: incorrect");
            Assert.IsTrue(settings.PrettyPrint, "PrettyPrint: incorrect");
            Assert.AreEqual(10, settings.PrettyPrintLineLength, "PrettyPrintLineLength: incorrect");
            Assert.AreEqual("App.Config.Settings.Alternate.xls", settings.SettingsFile, "SettingsFile: incorrect");
            Assert.IsTrue(settings.FlatDirectoryOutput, "FlatDirectoryOutput: incorrect");
            Assert.AreEqual("App.Config.Template.Alternate.xml", settings.TemplateFile, "TemplateFile: incorrect");
            Assert.IsTrue(settings.WriteUnchangedFiles, "WriteUnchangedFiles: incorrect");
            Assert.IsTrue(settings.Verbose, "Verbose: incorrect");
        }

        /// <summary>
        /// Asserts that if warnings are generated and failOnWarnings is false, NAnt reports success
        /// </summary>
        [Test]
        public void FailOnWarnings001_FailOnWarningsNotSet()
        {
            using (var testDir = new DisposableDirectory(false))
            {
                const string buildFileContents = @"
<project name=""ConfigGen NAnt Task"" default=""test"" basedir=""."">
  <target name=""test"">
    <loadtasks assembly=""ConfigGen.Applications.dll"" />
    <configgen
        settingsFile=""App.Config.Settings.xls""
        templateFile=""Bad.App.Config.Template.xml""
        failOnWarnings=""false""
        verbose=""true""/>
  </target>  
</project>";

                // this template has the token [%EnvironmentProperty%] instead of the property [%Environment%] specified in the settings file.
                const string templateContents =
                    @"<?xml version=""1.0"" encoding=""utf-8""?>
<configuration xmlns:cg=""http://roblevine.co.uk/Namespaces/ConfigGen/1/0/"">
  <appSettings>
    <add key=""Environment"" value=""[%EnvironmentProperty%]"" />
  </appSettings>
  <system.web>
    <customErrors mode=""[%CustomErrorMode%]"" />
  </system.web>
</configuration>";

                File.WriteAllText("Bad.App.Config.Template.xml", templateContents);

                var results = ExecuteNAntTask(testDir.FullName, buildFileContents);
                Assert.AreEqual(0, results.ExitCode, "Incorrect exit code from NAnt");
            }
        }

        /// <summary>
        /// Asserts that if warnings are generated and failOnWarnings is true, NAnt reports failure
        /// </summary>
        [Test]
        public void FailOnWarnings002_FailOnWarningsSet()
        {
            using (var testDir = new DisposableDirectory(false))
            {
                const string buildFileContents = @"
<project name=""ConfigGen NAnt Task"" default=""test"" basedir=""."">
  <target name=""test"">
    <loadtasks assembly=""ConfigGen.Applications.dll"" />
    <configgen
        settingsFile=""App.Config.Settings.xls""
        templateFile=""Bad.App.Config.Template.xml""
        failOnWarnings=""true""
        verbose=""true""/>
  </target>  
</project>";

                // this template has the token [%EnvironmentProperty%] instead of the property [%Environment%] specified in the settings file.
                const string templateContents =
                    @"<?xml version=""1.0"" encoding=""utf-8""?>
<configuration xmlns:cg=""http://roblevine.co.uk/Namespaces/ConfigGen/1/0/"">
  <appSettings>
    <add key=""Environment"" value=""[%EnvironmentProperty%]"" />
  </appSettings>
  <system.web>
    <customErrors mode=""[%CustomErrorMode%]"" />
  </system.web>
</configuration>";

                File.WriteAllText("Bad.App.Config.Template.xml", templateContents);

                var results = ExecuteNAntTask(testDir.FullName, buildFileContents);
                Assert.AreNotEqual(0, results.ExitCode, "Incorrect exit code from NAnt");
            }
        }

        public class ExecuteResults
        {
            public int ExitCode { get; set; }
            public string StdOut { get; set; }
            public string StdErr { get; set; }
            public Preferences Settings { get; set; }
        }

        private ExecuteResults ExecuteTest(string buildFileContents)
        {
            using (var disposableDirectory = new DisposableDirectory())
            {
                var dumpedConfigSettingsFile = Path.Combine(disposableDirectory.FullName, "configGenDumpedSettings.xml");
                var results = ExecuteNAntTask(disposableDirectory.FullName, string.Format(buildFileContents, dumpedConfigSettingsFile));
                results.Settings = LoadConfigurationFromFile(dumpedConfigSettingsFile);
                return results;
            }
        }

        private ExecuteResults ExecuteNAntTask(string directory, string buildFileContents)
        {
            File.WriteAllText("nant.build", buildFileContents);
            var exeDirectory = new DirectoryInfo(".");
            exeDirectory.CopyContentsTo(directory, false, false);
            var nantExe = new FileInfo(@"..\..\..\packages\NAnt.Portable.0.92\NAnt.exe");
            var processStartInfo = new ProcessStartInfo
                                       {
                                           FileName = nantExe.FullName,
                                           WorkingDirectory = directory,
                                           RedirectStandardError = true,
                                           RedirectStandardOutput = true,
                                           UseShellExecute = false
                                       };

            var process = new Process();

            process.StartInfo = processStartInfo;
            process.EnableRaisingEvents = true;

            Console.WriteLine("cfg.exe " + processStartInfo.Arguments);

            process.Start();

            var stdOut = process.StandardOutput.ReadToEnd();
            var stdErr = process.StandardError.ReadToEnd();
            bool testCompleted = process.WaitForExit(5000);
            
            var ret = new ExecuteResults
            {
                ExitCode = process.ExitCode,
                StdOut = stdOut,
                StdErr = stdErr
            };

            Console.WriteLine("StandardOut:");
            Console.WriteLine(ret.StdOut);
            Console.WriteLine("StandardError");
            Console.WriteLine(ret.StdErr);

            Assert.IsTrue(testCompleted, "Test timed out");
            
            return ret;
        }


        private Preferences LoadConfigurationFromFile(string fileName)
        {
            //Commented out the below check because this is test code and the method is private
            //if (!File.Exists(fileName))
            //{
            //    return null;
            //}

            var xmlSerializer = new XmlSerializer(typeof(Preferences));

            using (var fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                return (Preferences)xmlSerializer.Deserialize(fileStream);
            }
        }
    }
}
